package com.zlx.mall.basic.signature;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.util.DigestUtils;
import org.springframework.util.ObjectUtils;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Duration;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collectors;

/**
 * @author qingke
 **/
@Data
@NoArgsConstructor
public abstract class AbstractSignature {

    private static final String AND = "&";
    private static final String EQUAL = "=";

    @SignatureField
    @NotNull(message = "Illegal Signature Param")
    protected Long timestamp;
    @NotBlank(message = "Illegal Signature Param")
    protected String signature;

    @JsonIgnore
    public String generateSignature(String secret) {
        SortedMap<String, String> params = new TreeMap<>();
        for (Field field : FieldUtils.getFieldsListWithAnnotation(this.getClass(), SignatureField.class)) {
            try {
                params.put(field.getName(), ObjectUtils.getDisplayString(FieldUtils.readField(field, this, true)));
            } catch (Exception ignore) {
                // ignore
            }
        }

        String raw = params.entrySet().stream()
                .map(entry -> entry.getKey().concat(EQUAL).concat(entry.getValue()))
                .collect(Collectors.joining(AND));

        return DigestUtils.md5DigestAsHex((raw + secret).getBytes(StandardCharsets.UTF_8));
    }

    @JsonIgnore
    public boolean verifySignature(String secret, Duration timestampTtl) {
        if (this.timestamp < System.currentTimeMillis() - timestampTtl.toMillis()) {
            return false;
        }
        String signature = generateSignature(secret);
        return MessageDigest.isEqual(signature.getBytes(StandardCharsets.UTF_8), this.signature.getBytes(StandardCharsets.UTF_8));
    }

}
